﻿#include "precompiled.h"
#include "PropVariant.h"
#include <algorithm>
#include <cstdlib>

unsigned INCLUDE_TYPE = 0x00;
void writeBlockNameToConsole ( IWICMetadataReaderPtr reader, int depth );
void writeError ( char * errorMessage, HRESULT errorCode );
void walkMetadataReadersAndItems ( IWICMetadataReaderPtr reader, int depth );

int wmain ( int argc, wchar_t *argv[] )
{
    _setmode ( _fileno ( stdout ), _O_U16TEXT ); // enable unicode in console

	std::wcout << "\n\n" << "version " << __DATE__ << " " << __TIME__ << "\n";

    // if no path specified, show usage info
    if ( argc < 2 ) {
        std::wcout << "\n\tNo image path specified!\n\n";
        std::wcout << "USAGE\n\n" \
                   "MetadataReader.exe FILE [-i]\n\n"	\
                   "\tFILE\tpath to the image e.g. \"c:\\funnyCat.jpg\"\n"	\
                   "\t-i\t(optional argument) shows data types" \
                   "\n\n";

        exit ( 0 );
    }

    // check if file exists
    std::wstring imagePath ( argv[1] );
    std::ifstream f;
    f.open ( imagePath );
    if ( f ) {
        f.close();
    } else {
        std::wcout << "\nFile does not exists!\n";
        exit ( 0 );
    }

    // get optional argument
    if ( argc == 3 ) {
        std::wstring includeTypeArg ( argv[2] );
        if ( includeTypeArg == L"-i" ) {
            INCLUDE_TYPE = 0x01;
        }
    }

    std::wcout << "\nRead image metadata from file: " << imagePath << std::wstring ( 2, '\n' );

    HRESULT hr = CoInitializeEx ( NULL, COINIT_MULTITHREADED ); // Initialize COM

    if ( FAILED ( hr ) ) {
        writeError ( "COM initialisation failed", hr );
        exit ( 1 );
    }

    std::wcout << "Create the COM imaging factory\n";
    IWICImagingFactoryPtr pFactory;
    hr = CoCreateInstance (
             CLSID_WICImagingFactory1,
             NULL,
             CLSCTX_INPROC_SERVER,
             IID_PPV_ARGS ( &pFactory )
         );

    if ( FAILED ( hr ) ) {
        writeError ( "Creating the COM imaging factory failed", hr );
        exit ( 1 );
    }

    std::wcout << "Create the decoder\n";
    IWICBitmapDecoderPtr pDecoder ;
    hr = pFactory->CreateDecoderFromFilename (
             imagePath.c_str(), // Image to be decoded
             NULL,						// Do not prefer a particular vendor
             GENERIC_READ,				// Desired read access to the file
             WICDecodeMetadataCacheOnLoad, // Cache metadata when needed
             &pDecoder );					// Pointer to the decoder
	
    if ( FAILED ( hr ) ) {
        writeError ( "Creating decoder failed", hr );
        exit ( 1 );
    }

    std::wcout << "Get a single frame from the image\n";
    IWICBitmapFrameDecodePtr pFrameDecode ;
    hr = pDecoder->GetFrame (
             0,  //JPEG has only one frame.
             &pFrameDecode );

    if ( FAILED ( hr ) ) {
        writeError ( "Frame decoding failed", hr );
        exit ( 1 );
    }

    std::wcout << "Get the metadata block reader from the frame\n";
    IWICMetadataBlockReaderPtr pBlockReader;
    hr = pFrameDecode.Get()->QueryInterface ( IID_IWICMetadataBlockReader, ( void** ) &pBlockReader );

    if ( FAILED ( hr ) ) {
        writeError ( "Metadata block reader failed", hr );
        exit ( 1 );
    }

    UINT countBlocks;
    hr = pBlockReader->GetCount ( &countBlocks );

    if ( FAILED ( hr ) ) {
        writeError ( "Error retrieving number of blocks!", hr );
        exit ( 1 );
    }

    std::wcout << "Begin to iterate over all metadata blocks\n\n";

    for ( UINT iBlock = 0; iBlock < countBlocks; iBlock++ ) {
        IWICMetadataReader * reader;
        hr = pBlockReader->GetReaderByIndex ( iBlock, &reader );
        if ( SUCCEEDED ( hr ) ) {
            walkMetadataReadersAndItems ( reader, 0 );
        }
    }

    return 0;
}


void writeError ( char * errorMessage, HRESULT errorCode )
{
    const char *  errorValue ( "?" );
    for each ( const auto & err in WIC_ERRORS ) {
        if ( err.code == errorCode ) {
            errorValue = err.value;
            break;
        }
    }

    std::wcout << "\n" << errorMessage << ". Error value/code: " << \
               errorValue << "/" << std::hex << errorCode << "\n";
}


void writeBlockNameToConsole ( IWICMetadataReaderPtr  reader, int depth )
{
    IWICMetadataHandlerInfoPtr info;
    CString infoString;
    HRESULT result = reader->GetMetadataHandlerInfo ( &info );
    if ( SUCCEEDED ( result ) ) {
        READ_WIC_STRING ( info->GetFriendlyName, infoString );
    }

    static HANDLE consoleHandle;
    if ( !consoleHandle )
        consoleHandle = GetStdHandle ( STD_OUTPUT_HANDLE );

    // colorize console
    SetConsoleTextAttribute ( consoleHandle,  BACKGROUND_GREEN  );
    std::wcout << std::wstring ( depth, '\t' ) << infoString.GetString() ;
    SetConsoleTextAttribute ( consoleHandle, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED );
    std::wcout <<  L"\n";
}


// walks recursively all metadata readers and items in a metadata block
void walkMetadataReadersAndItems ( IWICMetadataReaderPtr reader, int depth )
{
    UINT countItems;
    HRESULT result = reader->GetCount ( &countItems );
    PROPVARIANT  propId, propValue;

    if ( FAILED ( result ) ) {
        writeError ( "could not read block", result );
        return;
    }

    writeBlockNameToConsole ( reader, depth );

    for ( UINT iItem = 0; iItem < countItems; iItem++ ) {
        PropVariantInit ( &propId );
        PropVariantInit ( &propValue );
		
		IWICMetadataReaderPtr queryReader;
        result = reader->GetValueByIndex ( iItem, NULL, &propId, &propValue );

        if ( FAILED ( result ) ) continue;

        if ( propValue.vt == VT_UNKNOWN ) {
            IWICMetadataReaderPtr reader2;
            result = propValue.punkVal->QueryInterface ( IID_IWICMetadataReader, ( void** ) &reader2 );
			
			if ( SUCCEEDED ( result ) )  {
                walkMetadataReadersAndItems ( reader2, depth + 1 ); // recursive call
            }
        } else {
            CStringW s, id, v;
            PropVariantToString ( &propId, INCLUDE_TYPE, id );
            PropVariantToString ( &propValue, INCLUDE_TYPE, v );
            std::wcout << std::wstring ( depth, '\t' ) << L"\t" << \
                       id.GetString() << "   " << \
                       v.GetString() << "\n";
        }

        PropVariantClear ( &propId );
        PropVariantClear ( &propValue );
    }
    std::wcout << L"\n";
}
